using System;
using System.Diagnostics;
using System.Collections.Generic;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.nioneo.xa
{


	using XAException = javax.transaction.xa.XAException;

	using Node = org.neo4j.graphdb.Node;
	using Relationship = org.neo4j.graphdb.Relationship;
	using RelationshipType = org.neo4j.graphdb.RelationshipType;
	using LockReleaser = org.neo4j.kernel.impl.core.LockReleaser;
	using PropertyIndex = org.neo4j.kernel.impl.core.PropertyIndex;
	using DynamicRecord = org.neo4j.kernel.impl.nioneo.store.DynamicRecord;
	using InvalidRecordException = org.neo4j.kernel.impl.nioneo.store.InvalidRecordException;
	using NeoStore = org.neo4j.kernel.impl.nioneo.store.NeoStore;
	using NodeRecord = org.neo4j.kernel.impl.nioneo.store.NodeRecord;
	using NodeStore = org.neo4j.kernel.impl.nioneo.store.NodeStore;
	using PropertyData = org.neo4j.kernel.impl.nioneo.store.PropertyData;
	using PropertyIndexData = org.neo4j.kernel.impl.nioneo.store.PropertyIndexData;
	using PropertyIndexRecord = org.neo4j.kernel.impl.nioneo.store.PropertyIndexRecord;
	using PropertyIndexStore = org.neo4j.kernel.impl.nioneo.store.PropertyIndexStore;
	using PropertyRecord = org.neo4j.kernel.impl.nioneo.store.PropertyRecord;
	using PropertyStore = org.neo4j.kernel.impl.nioneo.store.PropertyStore;
	using PropertyType = org.neo4j.kernel.impl.nioneo.store.PropertyType;
	using Record = org.neo4j.kernel.impl.nioneo.store.Record;
	using RelationshipChainPosition = org.neo4j.kernel.impl.nioneo.store.RelationshipChainPosition;
	using RelationshipData = org.neo4j.kernel.impl.nioneo.store.RelationshipData;
	using RelationshipRecord = org.neo4j.kernel.impl.nioneo.store.RelationshipRecord;
	using RelationshipStore = org.neo4j.kernel.impl.nioneo.store.RelationshipStore;
	using RelationshipTypeData = org.neo4j.kernel.impl.nioneo.store.RelationshipTypeData;
	using RelationshipTypeRecord = org.neo4j.kernel.impl.nioneo.store.RelationshipTypeRecord;
	using RelationshipTypeStore = org.neo4j.kernel.impl.nioneo.store.RelationshipTypeStore;
	using PropertyCommand = org.neo4j.kernel.impl.nioneo.xa.Command.PropertyCommand;
	using LockManager = org.neo4j.kernel.impl.transaction.LockManager;
	using LockType = org.neo4j.kernel.impl.transaction.LockType;
	using XaCommand = org.neo4j.kernel.impl.transaction.xaframework.XaCommand;
	using XaLogicalLog = org.neo4j.kernel.impl.transaction.xaframework.XaLogicalLog;
	using XaTransaction = org.neo4j.kernel.impl.transaction.xaframework.XaTransaction;
	using ArrayMap = org.neo4j.kernel.impl.util.ArrayMap;

///
/// <summary> * Transaction containing <seealso cref="Command commands"/> reflecting the operations
/// * performed in the transaction. </summary>
/// 
	internal class NeoTransaction : XaTransaction
	{
		private readonly Map<int, NodeRecord> nodeRecords = new Dictionary<int, NodeRecord>();
		private readonly Map<int, PropertyRecord> propertyRecords = new Dictionary<int, PropertyRecord>();
		private readonly Map<int, RelationshipRecord> relRecords = new Dictionary<int, RelationshipRecord>();
		private readonly Map<int, RelationshipTypeRecord> relTypeRecords = new Dictionary<int, RelationshipTypeRecord>();
		private readonly Map<int, PropertyIndexRecord> propIndexRecords = new Dictionary<int, PropertyIndexRecord>();

		private readonly List<Command.NodeCommand> nodeCommands = new List<Command.NodeCommand>();
		private readonly List<Command.PropertyCommand> propCommands = new List<Command.PropertyCommand>();
		private readonly List<Command.PropertyIndexCommand> propIndexCommands = new List<Command.PropertyIndexCommand>();
		private readonly List<Command.RelationshipCommand> relCommands = new List<Command.RelationshipCommand>();
		private readonly List<Command.RelationshipTypeCommand> relTypeCommands = new List<Command.RelationshipTypeCommand>();

		private readonly NeoStore neoStore;
		private bool committed = false;
		private bool prepared = false;

		private readonly LockReleaser lockReleaser;
		private readonly LockManager lockManager;

		internal NeoTransaction(int identifier, XaLogicalLog log, NeoStore neoStore, LockReleaser lockReleaser, LockManager lockManager) : base(identifier, log)
		{
			this.neoStore = neoStore;
			this.lockReleaser = lockReleaser;
			this.lockManager = lockManager;
		}

		public override bool isReadOnly()
		{
			if (isRecovered())
			{
				if (nodeCommands.Count == 0 && propCommands.Count == 0 && relCommands.Count == 0 && relTypeCommands.Count == 0 && propIndexCommands.Count == 0)
				{
					return true;
				}
				return false;
			}
			if (nodeRecords.size() == 0 && relRecords.size() == 0 && relTypeRecords.size() == 0 && propertyRecords.size() == 0 && propIndexRecords.size() == 0)
			{
				return true;
			}
			return false;
		}

		public override void doAddCommand(XaCommand command)
		{
		// override
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: protected void doPrepare() throws XAException
		protected internal override void doPrepare()
		{
			if (committed)
			{
				throw new XAException("Cannot prepare committed transaction[" + getIdentifier() + "]");
			}
			if (prepared)
			{
				throw new XAException("Cannot prepare prepared transaction[" + getIdentifier() + "]");
			}
		// generate records then write to logical log via addCommand method
			prepared = true;
			foreach (RelationshipTypeRecord record in relTypeRecords.values())
			{
				Command.RelationshipTypeCommand command = new Command.RelationshipTypeCommand(neoStore.getRelationshipTypeStore(), record);
				relTypeCommands.Add(command);
				addCommand(command);
			}
			foreach (NodeRecord record in nodeRecords.values())
			{
				if (!record.inUse() && record.getNextRel() != Record.NO_NEXT_RELATIONSHIP.intValue())
				{
					throw new InvalidRecordException("Node record " + record + " still has relationships");
				}
				Command.NodeCommand command = new Command.NodeCommand(neoStore.getNodeStore(), record);
				nodeCommands.Add(command);
				if (!record.inUse())
				{
					removeNodeFromCache(record.getId());
				}
				addCommand(command);
			}
			foreach (RelationshipRecord record in relRecords.values())
			{
				Command.RelationshipCommand command = new Command.RelationshipCommand(neoStore.getRelationshipStore(), record);
				relCommands.Add(command);
				if (!record.inUse())
				{
					removeRelationshipFromCache(record.getId());
				}
				addCommand(command);
			}
			foreach (PropertyIndexRecord record in propIndexRecords.values())
			{
				Command.PropertyIndexCommand command = new Command.PropertyIndexCommand(neoStore.getPropertyStore().getIndexStore(), record);
				propIndexCommands.Add(command);
				addCommand(command);
			}
			foreach (PropertyRecord record in propertyRecords.values())
			{
				Command.PropertyCommand command = new Command.PropertyCommand(neoStore.getPropertyStore(), record);
				propCommands.Add(command);
				addCommand(command);
			}
		}

		protected internal override void injectCommand(XaCommand xaCommand)
		{
			if (xaCommand is Command.NodeCommand)
			{
				nodeCommands.Add((Command.NodeCommand) xaCommand);
			}
			else if (xaCommand is Command.RelationshipCommand)
			{
				relCommands.Add((Command.RelationshipCommand) xaCommand);
			}
			else if (xaCommand is Command.PropertyCommand)
			{
				propCommands.Add((Command.PropertyCommand) xaCommand);
			}
			else if (xaCommand is Command.PropertyIndexCommand)
			{
				propIndexCommands.Add((Command.PropertyIndexCommand) xaCommand);
			}
			else if (xaCommand is Command.RelationshipTypeCommand)
			{
				relTypeCommands.Add((Command.RelationshipTypeCommand) xaCommand);
			}
			else
			{
				throw new IllegalArgumentException("Unknown command " + xaCommand);
			}
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public void doRollback() throws XAException
		public override void doRollback()
		{
			if (committed)
			{
				throw new XAException("Cannot rollback partialy commited " + "transaction[" + getIdentifier() + "]. Recover and " + "commit");
			}
			try
			{
				foreach (RelationshipTypeRecord record in relTypeRecords.values())
				{
					if (record.isCreated())
					{
						getRelationshipTypeStore().freeId(record.getId());
						foreach (DynamicRecord dynamicRecord in record.getTypeRecords())
						{
							if (dynamicRecord.isCreated())
							{
								getRelationshipTypeStore().freeBlockId(dynamicRecord.getId());
							}
						}
					}
					removeRelationshipTypeFromCache(record.getId());
				}
				foreach (NodeRecord record in nodeRecords.values())
				{
					if (record.isCreated())
					{
						getNodeStore().freeId(record.getId());
					}
					removeNodeFromCache(record.getId());
				}
				foreach (RelationshipRecord record in relRecords.values())
				{
					if (record.isCreated())
					{
						getRelationshipStore().freeId(record.getId());
					}
					removeRelationshipFromCache(record.getId());
				}
				foreach (PropertyIndexRecord record in propIndexRecords.values())
				{
					if (record.isCreated())
					{
						getPropertyStore().getIndexStore().freeId(record.getId());
						foreach (DynamicRecord dynamicRecord in record.getKeyRecords())
						{
							if (dynamicRecord.isCreated())
							{
								getPropertyStore().getIndexStore().freeBlockId(dynamicRecord.getId());
							}
						}
					}
				}
				foreach (PropertyRecord record in propertyRecords.values())
				{
					if (record.getNodeId() != -1)
					{
						removeNodeFromCache(record.getNodeId());
					}
					else if (record.getRelId() != -1)
					{
						removeRelationshipFromCache(record.getRelId());
					}
					if (record.isCreated())
					{
						getPropertyStore().freeId(record.getId());
						foreach (DynamicRecord dynamicRecord in record.getValueRecords())
						{
							if (dynamicRecord.isCreated())
							{
								if (dynamicRecord.getType() == PropertyType.STRING.intValue())
								{
									getPropertyStore().freeStringBlockId(dynamicRecord.getId());
								}
								else if (dynamicRecord.getType() == PropertyType.ARRAY.intValue())
								{
									getPropertyStore().freeArrayBlockId(dynamicRecord.getId());
								}
								else
								{
									throw new InvalidRecordException("Unknown type on " + dynamicRecord);
								}
							}
						}
					}
				}
			}
			finally
			{
				nodeRecords.Clear();
				propertyRecords.Clear();
				relRecords.Clear();
				relTypeRecords.Clear();
				propIndexRecords.Clear();

				nodeCommands.Clear();
				propCommands.Clear();
				propIndexCommands.Clear();
				relCommands.Clear();
				relTypeCommands.Clear();
				if (!isRecovered())
				{
					lockReleaser.rollback();
				}
			}
		}

		private void removeRelationshipTypeFromCache(int id)
		{
			lockReleaser.removeRelationshipTypeFromCache(id);
		}

		private void removeRelationshipFromCache(int id)
		{
			lockReleaser.removeRelationshipFromCache(id);
		}

		private void removeNodeFromCache(int id)
		{
			lockReleaser.removeNodeFromCache(id);
		}

		private void addRelationshipType(int id)
		{
			setRecovered();
			RelationshipTypeData type;
			if (isRecovered())
			{
				type = neoStore.getRelationshipTypeStore().getRelationshipType(id, true);
			}
			else
			{
				type = neoStore.getRelationshipTypeStore().getRelationshipType(id);
			}
			lockReleaser.addRelationshipType(type);
		}

		private void addPropertyIndexCommand(int id)
		{
			PropertyIndexData index;
			if (isRecovered())
			{
				index = neoStore.getPropertyStore().getIndexStore().getPropertyIndex(id, true);
			}
			else
			{
				index = neoStore.getPropertyStore().getIndexStore().getPropertyIndex(id);
			}
			lockReleaser.addPropertyIndex(index);
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public void doCommit() throws XAException
		public override void doCommit()
		{
			if (!isRecovered() && !prepared)
			{
				throw new XAException("Cannot commit non prepared transaction[" + getIdentifier() + "]");
			}
			if (isRecovered())
			{
				commitRecovered();
				return;
			}
			try
			{
				committed = true;
				CommandSorter sorter = new CommandSorter();
			// reltypes
				java.util.Collections.sort(relTypeCommands, sorter);
				foreach (Command.RelationshipTypeCommand command in relTypeCommands)
				{
					command.execute();
				}
			// nodes
				java.util.Collections.sort(nodeCommands, sorter);
				foreach (Command.NodeCommand command in nodeCommands)
				{
					command.execute();
				}
			// relationships
				java.util.Collections.sort(relCommands, sorter);
				foreach (Command.RelationshipCommand command in relCommands)
				{
					command.execute();
				}
				java.util.Collections.sort(propIndexCommands, sorter);
				foreach (Command.PropertyIndexCommand command in propIndexCommands)
				{
					command.execute();
				}
			// properties
				java.util.Collections.sort(propCommands, sorter);
				foreach (Command.PropertyCommand command in propCommands)
				{
					command.execute();
				}
				if (!isRecovered())
				{
					lockReleaser.commit();
				}
			}
			finally
			{
				nodeRecords.Clear();
				propertyRecords.Clear();
				relRecords.Clear();
				relTypeRecords.Clear();
				propIndexRecords.Clear();

				nodeCommands.Clear();
				propCommands.Clear();
				propIndexCommands.Clear();
				relCommands.Clear();
				relTypeCommands.Clear();
			}
		}

		private void commitRecovered()
		{
			try
			{
				committed = true;
				CommandSorter sorter = new CommandSorter();
			// reltypes
				java.util.Collections.sort(relTypeCommands, sorter);
				foreach (Command.RelationshipTypeCommand command in relTypeCommands)
				{
					command.execute();
					addRelationshipType(command.Key);
				}
			// nodes
				java.util.Collections.sort(nodeCommands, sorter);
				foreach (Command.NodeCommand command in nodeCommands)
				{
					command.execute();
					removeNodeFromCache(command.Key);
				}
			// relationships
				java.util.Collections.sort(relCommands, sorter);
				foreach (Command.RelationshipCommand command in relCommands)
				{
					command.execute();
					removeRelationshipFromCache(command.Key);
				}
				java.util.Collections.sort(propIndexCommands, sorter);
				foreach (Command.PropertyIndexCommand command in propIndexCommands)
				{
					command.execute();
					addPropertyIndexCommand(command.Key);
				}
			// properties
				java.util.Collections.sort(propCommands, sorter);
				foreach (Command.PropertyCommand command in propCommands)
				{
					command.execute();
					removePropertyFromCache(command);
				}
				neoStore.updateIdGenerators();
				if (!isRecovered())
				{
					lockReleaser.commit();
				}
			}
			finally
			{
				nodeRecords.Clear();
				propertyRecords.Clear();
				relRecords.Clear();
				relTypeRecords.Clear();
				propIndexRecords.Clear();

				nodeCommands.Clear();
				propCommands.Clear();
				propIndexCommands.Clear();
				relCommands.Clear();
				relTypeCommands.Clear();
			}
		}


		private void removePropertyFromCache(PropertyCommand command)
		{
			lockReleaser.clearCache();
		}

		private RelationshipTypeStore getRelationshipTypeStore()
		{
			return neoStore.getRelationshipTypeStore();
		}

		private int getRelGrabSize()
		{
			return neoStore.getRelationshipGrabSize();
		}

		private NodeStore getNodeStore()
		{
			return neoStore.getNodeStore();
		}

		private RelationshipStore getRelationshipStore()
		{
			return neoStore.getRelationshipStore();
		}

		private PropertyStore getPropertyStore()
		{
			return neoStore.getPropertyStore();
		}

		public virtual bool nodeLoadLight(int nodeId)
		{
			NodeRecord nodeRecord = getNodeRecord(nodeId);
			if (nodeRecord != null)
			{
				return nodeRecord.inUse();
			}
			return getNodeStore().loadLightNode(nodeId);
		}

		public virtual RelationshipData relationshipLoad(int id)
		{
			RelationshipRecord relRecord = getRelationshipRecord(id);
			if (relRecord != null)
			{
				if (!relRecord.inUse())
				{
					return null;
				}
				return new RelationshipData(id, relRecord.getFirstNode(), relRecord.getSecondNode(), relRecord.getType());
			}
			relRecord = getRelationshipStore().getLightRel(id);
			if (relRecord != null)
			{
				return new RelationshipData(id, relRecord.getFirstNode(), relRecord.getSecondNode(), relRecord.getType());
			}
			return null;
		}

		internal virtual void nodeDelete(int nodeId)
		{
			NodeRecord nodeRecord = getNodeRecord(nodeId);
			if (nodeRecord == null)
			{
				nodeRecord = getNodeStore().getRecord(nodeId);
				addNodeRecord(nodeRecord);
			}
			if (!nodeRecord.inUse())
			{
				throw new IllegalStateException("Unable to delete Node[" + nodeId + "] since it has already been deleted.");
			}
			nodeRecord.setInUse(false);
			int nextProp = nodeRecord.getNextProp();
			while (nextProp != Record.NO_NEXT_PROPERTY.intValue())
			{
				PropertyRecord propRecord = getPropertyRecord(nextProp);
				if (propRecord == null)
				{
					propRecord = getPropertyStore().getRecord(nextProp);
					addPropertyRecord(propRecord);
				}
				if (propRecord.isLight())
				{
					getPropertyStore().makeHeavy(propRecord);
				}

				nextProp = propRecord.getNextProp();
				propRecord.setInUse(false);
			// TODO: update count on property index record
				foreach (DynamicRecord valueRecord in propRecord.getValueRecords())
				{
					valueRecord.setInUse(false);
				}
			}
		}

		internal virtual void relDelete(int id)
		{
			RelationshipRecord record = getRelationshipRecord(id);
			if (record == null)
			{
				record = getRelationshipStore().getRecord(id);
				addRelationshipRecord(record);
			}
			if (!record.inUse())
			{
				throw new IllegalStateException("Unable to delete relationship[" + id + "] since it is already deleted.");
			}
			int nextProp = record.getNextProp();
			while (nextProp != Record.NO_NEXT_PROPERTY.intValue())
			{
				PropertyRecord propRecord = getPropertyRecord(nextProp);
				if (propRecord == null)
				{
					propRecord = getPropertyStore().getRecord(nextProp);
					addPropertyRecord(propRecord);
				}
				if (propRecord.isLight())
				{
					getPropertyStore().makeHeavy(propRecord);
				}
				nextProp = propRecord.getNextProp();
				propRecord.setInUse(false);
			// TODO: update count on property index record
				foreach (DynamicRecord valueRecord in propRecord.getValueRecords())
				{
					valueRecord.setInUse(false);
				}
			}
			disconnectRelationship(record);
			updateNodes(record);
			record.setInUse(false);
		}

		private void disconnectRelationship(RelationshipRecord rel)
		{
		// update first node prev
			if (rel.getFirstPrevRel() != Record.NO_NEXT_RELATIONSHIP.intValue())
			{
				Relationship lockableRel = new LockableRelationship(rel.getFirstPrevRel());
				getWriteLock(lockableRel);
				RelationshipRecord prevRel = getRelationshipRecord(rel.getFirstPrevRel());
				if (prevRel == null)
				{
					prevRel = getRelationshipStore().getRecord(rel.getFirstPrevRel());
					addRelationshipRecord(prevRel);
				}
				if (prevRel.getFirstNode() == rel.getFirstNode())
				{
					prevRel.setFirstNextRel(rel.getFirstNextRel());
				}
				else if (prevRel.getSecondNode() == rel.getFirstNode())
				{
					prevRel.setSecondNextRel(rel.getFirstNextRel());
				}
				else
				{
					throw new InvalidRecordException(prevRel + " don't match " + rel);
				}
			}
		// update first node next
			if (rel.getFirstNextRel() != Record.NO_NEXT_RELATIONSHIP.intValue())
			{
				Relationship lockableRel = new LockableRelationship(rel.getFirstNextRel());
				getWriteLock(lockableRel);
				RelationshipRecord nextRel = getRelationshipRecord(rel.getFirstNextRel());
				if (nextRel == null)
				{
					nextRel = getRelationshipStore().getRecord(rel.getFirstNextRel());
					addRelationshipRecord(nextRel);
				}
				if (nextRel.getFirstNode() == rel.getFirstNode())
				{
					nextRel.setFirstPrevRel(rel.getFirstPrevRel());
				}
				else if (nextRel.getSecondNode() == rel.getFirstNode())
				{
					nextRel.setSecondPrevRel(rel.getFirstPrevRel());
				}
				else
				{
					throw new InvalidRecordException(nextRel + " don't match " + rel);
				}
			}
		// update second node prev
			if (rel.getSecondPrevRel() != Record.NO_NEXT_RELATIONSHIP.intValue())
			{
				Relationship lockableRel = new LockableRelationship(rel.getSecondPrevRel());
				getWriteLock(lockableRel);
				RelationshipRecord prevRel = getRelationshipRecord(rel.getSecondPrevRel());
				if (prevRel == null)
				{
					prevRel = getRelationshipStore().getRecord(rel.getSecondPrevRel());
					addRelationshipRecord(prevRel);
				}
				if (prevRel.getFirstNode() == rel.getSecondNode())
				{
					prevRel.setFirstNextRel(rel.getSecondNextRel());
				}
				else if (prevRel.getSecondNode() == rel.getSecondNode())
				{
					prevRel.setSecondNextRel(rel.getSecondNextRel());
				}
				else
				{
					throw new InvalidRecordException(prevRel + " don't match " + rel);
				}
			}
		// update second node next
			if (rel.getSecondNextRel() != Record.NO_NEXT_RELATIONSHIP.intValue())
			{
				Relationship lockableRel = new LockableRelationship(rel.getSecondNextRel());
				getWriteLock(lockableRel);
				RelationshipRecord nextRel = getRelationshipRecord(rel.getSecondNextRel());
				if (nextRel == null)
				{
					nextRel = getRelationshipStore().getRecord(rel.getSecondNextRel());
					addRelationshipRecord(nextRel);
				}
				if (nextRel.getFirstNode() == rel.getSecondNode())
				{
					nextRel.setFirstPrevRel(rel.getSecondPrevRel());
				}
				else if (nextRel.getSecondNode() == rel.getSecondNode())
				{
					nextRel.setSecondPrevRel(rel.getSecondPrevRel());
				}
				else
				{
					throw new InvalidRecordException(nextRel + " don't match " + rel);
				}
			}
		}

		private void getWriteLock(Relationship lockableRel)
		{
			lockManager.getWriteLock(lockableRel);
			lockReleaser.addLockToTransaction(lockableRel, LockType.WRITE);
		}

		public virtual RelationshipChainPosition getRelationshipChainPosition(int nodeId)
		{
			NodeRecord nodeRecord = getNodeRecord(nodeId);
			if (nodeRecord == null)
			{
				nodeRecord = getNodeStore().getRecord(nodeId);
			}
			else if (!nodeRecord.inUse())
			{
				return new RelationshipChainPosition(Record.NO_NEXT_RELATIONSHIP.intValue());
			}
			int nextRel = nodeRecord.getNextRel();
			return new RelationshipChainPosition(nextRel);
		}

		public virtual IEnumerable <RelationshipData> getMoreRelationships(int nodeId, RelationshipChainPosition position)
		{
			int nextRel = position.getNextRecord();
			List<RelationshipData> rels = new List<RelationshipData>();
			for (int i = 0; i < getRelGrabSize() && nextRel != Record.NO_NEXT_RELATIONSHIP.intValue(); i++)
			{
				RelationshipRecord relRecord = getRelationshipRecord(nextRel);
				if (relRecord == null)
				{
					relRecord = getRelationshipStore().getChainRecord(nextRel);
				}
				if (relRecord == null)
				{
				// return what we got so far
					position.setNextRecord(Record.NO_NEXT_RELATIONSHIP.intValue());
					return rels;
				}
				int firstNode = relRecord.getFirstNode();
				int secondNode = relRecord.getSecondNode();
				if (relRecord.inUse() && !relRecord.isCreated())
				{
					rels.Add(new RelationshipData(relRecord.getId(), firstNode, secondNode, relRecord.getType()));
				}
				else
				{
					i--;
				}
				if (firstNode == nodeId)
				{
					nextRel = relRecord.getFirstNextRel();
				}
				else if (secondNode == nodeId)
				{
					nextRel = relRecord.getSecondNextRel();
				}
				else
				{
					throw new InvalidRecordException("Node[" + nodeId + "] not part of firstNode[" + firstNode + "] or secondNode[" + secondNode + "]");
				}
			}
			position.setNextRecord(nextRel);
			return rels;
		}

		private void updateNodes(RelationshipRecord rel)
		{
			if (rel.getFirstPrevRel() == Record.NO_PREV_RELATIONSHIP.intValue())
			{
				NodeRecord firstNode = getNodeRecord(rel.getFirstNode());
				if (firstNode == null)
				{
					firstNode = getNodeStore().getRecord(rel.getFirstNode());
					addNodeRecord(firstNode);
				}
				firstNode.setNextRel(rel.getFirstNextRel());
			}
			if (rel.getSecondPrevRel() == Record.NO_PREV_RELATIONSHIP.intValue())
			{
				NodeRecord secondNode = getNodeRecord(rel.getSecondNode());
				if (secondNode == null)
				{
					secondNode = getNodeStore().getRecord(rel.getSecondNode());
					addNodeRecord(secondNode);
				}
				secondNode.setNextRel(rel.getSecondNextRel());
			}
		}

		internal virtual void relRemoveProperty(int relId, int propertyId)
		{
			RelationshipRecord relRecord = getRelationshipRecord(relId);
			if (relRecord == null)
			{
				relRecord = getRelationshipStore().getRecord(relId);
			}
			if (!relRecord.inUse())
			{
				throw new IllegalStateException("Property remove on relationship[" + relId + "] illegal since it has been deleted.");
			}
			PropertyRecord propRecord = getPropertyRecord(propertyId);
			if (propRecord == null)
			{
				propRecord = getPropertyStore().getRecord(propertyId);
				addPropertyRecord(propRecord);
			}
			if (!propRecord.inUse())
			{
				throw new IllegalStateException("Unable to delete property[" + propertyId + "] since it is already deleted.");
			}
			propRecord.setRelId(relId);
			if (propRecord.isLight())
			{
				getPropertyStore().makeHeavy(propRecord);
			}

			propRecord.setInUse(false);
		// TODO: update count on property index record
			foreach (DynamicRecord valueRecord in propRecord.getValueRecords())
			{
				if (valueRecord.inUse())
				{
					valueRecord.setInUse(false, propRecord.getType().intValue());
				}
			}
			int prevProp = propRecord.getPrevProp();
			int nextProp = propRecord.getNextProp();
			if (relRecord.getNextProp() == propertyId)
			{
				relRecord.setNextProp(nextProp);
			// re-adding not a problem
				addRelationshipRecord(relRecord);
			}
			if (prevProp != Record.NO_PREVIOUS_PROPERTY.intValue())
			{
				PropertyRecord prevPropRecord = getPropertyRecord(prevProp);
				if (prevPropRecord == null)
				{
					prevPropRecord = getPropertyStore().getLightRecord(prevProp);
					addPropertyRecord(prevPropRecord);
				}
				Debug.Assert(prevPropRecord.inUse());
				prevPropRecord.setNextProp(nextProp);
			}
			if (nextProp != Record.NO_NEXT_PROPERTY.intValue())
			{
				PropertyRecord nextPropRecord = getPropertyRecord(nextProp);
				if (nextPropRecord == null)
				{
					nextPropRecord = getPropertyStore().getLightRecord(nextProp);
					addPropertyRecord(nextPropRecord);
				}
				Debug.Assert(nextPropRecord.inUse());
				nextPropRecord.setPrevProp(prevProp);
			}
		}

		public virtual ArrayMap<int, PropertyData> relGetProperties(int relId)
		{
			RelationshipRecord relRecord = getRelationshipRecord(relId);
			if (relRecord == null)
			{
				relRecord = getRelationshipStore().getRecord(relId);
			}
			if (!relRecord.inUse())
			{
				throw new InvalidRecordException("Relationship[" + relId + "] not in use");
			}
			int nextProp = relRecord.getNextProp();
			ArrayMap<int, PropertyData> propertyMap = new ArrayMap<int, PropertyData>(9, false, true);
			while (nextProp != Record.NO_NEXT_PROPERTY.intValue())
			{
				PropertyRecord propRecord = getPropertyRecord(nextProp);
				if (propRecord == null)
				{
					propRecord = getPropertyStore().getLightRecord(nextProp);
				}
				if (!propRecord.isCreated())
				{
					propertyMap.put(propRecord.getKeyIndexId(), new PropertyData(propRecord.getId(), propertyGetValueOrNull(propRecord)));
				}
				nextProp = propRecord.getNextProp();
			}
			return propertyMap;
		}

		internal virtual ArrayMap<int, PropertyData> nodeGetProperties(int nodeId)
		{
			NodeRecord nodeRecord = getNodeRecord(nodeId);
			if (nodeRecord == null)
			{
				nodeRecord = getNodeStore().getRecord(nodeId);
			}
			else if (!nodeRecord.inUse())
			{
				throw new InvalidRecordException("Node[" + nodeId + "] not in use");
			}

			int nextProp = nodeRecord.getNextProp();
			ArrayMap<int, PropertyData> propertyMap = new ArrayMap<int, PropertyData>(9, false, true);
			while (nextProp != Record.NO_NEXT_PROPERTY.intValue())
			{
				PropertyRecord propRecord = getPropertyRecord(nextProp);
				if (propRecord == null)
				{
					propRecord = getPropertyStore().getLightRecord(nextProp);
				}
				if (!propRecord.isCreated())
				{
					propertyMap.put(propRecord.getKeyIndexId(), new PropertyData(propRecord.getId(), propertyGetValueOrNull(propRecord)));
				}
				nextProp = propRecord.getNextProp();
			}
			return propertyMap;
		}

		public virtual object propertyGetValueOrNull(PropertyRecord propertyRecord)
		{
			PropertyType type = propertyRecord.getType();
			if (type == PropertyType.INT)
			{
				return (int) propertyRecord.getPropBlock();
			}
			if (type == PropertyType.STRING)
			{
				return null;
			}
			if (type == PropertyType.BOOL)
			{
				if (propertyRecord.getPropBlock() == 1)
				{
					return Convert.ToBoolean(true);
				}
				return Convert.ToBoolean(false);
			}
			if (type == PropertyType.DOUBLE)
			{
				return new double(double.longBitsToDouble(propertyRecord.getPropBlock()));
			}
			if (type == PropertyType.FLOAT)
			{
				return new float(float.intBitsToFloat((int) propertyRecord.getPropBlock()));
			}
			if (type == PropertyType.LONG)
			{
				return propertyRecord.getPropBlock();
			}
			if (type == PropertyType.BYTE)
			{
				return (sbyte) propertyRecord.getPropBlock();
			}
			if (type == PropertyType.CHAR)
			{
				return (char) propertyRecord.getPropBlock();
			}
			if (type == PropertyType.ARRAY)
			{
				return null;
			}
			if (type == PropertyType.SHORT)
			{
				return (short) propertyRecord.getPropBlock();
			}
			throw new InvalidRecordException("Unknown type: " + type + " on " + propertyRecord);
		}

		public virtual object propertyGetValue(int id)
		{
			PropertyRecord propertyRecord = getPropertyRecord(id);
			if (propertyRecord == null)
			{
				propertyRecord = getPropertyStore().getRecord(id);
			}
			if (propertyRecord.isLight())
			{
				getPropertyStore().makeHeavy(propertyRecord);
			}
			PropertyType type = propertyRecord.getType();
			if (type == PropertyType.INT)
			{
				return (int) propertyRecord.getPropBlock();
			}
			if (type == PropertyType.STRING)
			{
				return getPropertyStore().getStringFor(propertyRecord);
			}
			if (type == PropertyType.BOOL)
			{
				if (propertyRecord.getPropBlock() == 1)
				{
					return Convert.ToBoolean(true);
				}
				return Convert.ToBoolean(false);
			}
			if (type == PropertyType.DOUBLE)
			{
				return new double(double.longBitsToDouble(propertyRecord.getPropBlock()));
			}
			if (type == PropertyType.FLOAT)
			{
				return new float(float.intBitsToFloat((int) propertyRecord.getPropBlock()));
			}
			if (type == PropertyType.LONG)
			{
				return propertyRecord.getPropBlock();
			}
			if (type == PropertyType.BYTE)
			{
				return (sbyte) propertyRecord.getPropBlock();
			}
			if (type == PropertyType.CHAR)
			{
				return (char) propertyRecord.getPropBlock();
			}
			if (type == PropertyType.ARRAY)
			{
				return getPropertyStore().getArrayFor(propertyRecord);
			}
			if (type == PropertyType.SHORT)
			{
				return (short) propertyRecord.getPropBlock();
			}
			throw new InvalidRecordException("Unknown type: " + type + " on " + propertyRecord);
		}

		internal virtual void nodeRemoveProperty(int nodeId, int propertyId)
		{
			NodeRecord nodeRecord = getNodeRecord(nodeId);
			if (nodeRecord == null)
			{
				nodeRecord = getNodeStore().getRecord(nodeId);
			}
			if (!nodeRecord.inUse())
			{
				throw new IllegalStateException("Property remove on node[" + nodeId + "] illegal since it has been deleted.");
			}
			PropertyRecord propRecord = getPropertyRecord(propertyId);
			if (propRecord == null)
			{
				propRecord = getPropertyStore().getRecord(propertyId);
				addPropertyRecord(propRecord);
			}
			if (!propRecord.inUse())
			{
				throw new IllegalStateException("Unable to delete property[" + propertyId + "] since it is already deleted.");
			}
			propRecord.setNodeId(nodeId);
			if (propRecord.isLight())
			{
				getPropertyStore().makeHeavy(propRecord);
			}

			propRecord.setInUse(false);
		// TODO: update count on property index record
			foreach (DynamicRecord valueRecord in propRecord.getValueRecords())
			{
				if (valueRecord.inUse())
				{
					valueRecord.setInUse(false, propRecord.getType().intValue());
				}
			}
			int prevProp = propRecord.getPrevProp();
			int nextProp = propRecord.getNextProp();
			if (nodeRecord.getNextProp() == propertyId)
			{
				nodeRecord.setNextProp(nextProp);
			// re-adding not a problem
				addNodeRecord(nodeRecord);
			}
			if (prevProp != Record.NO_PREVIOUS_PROPERTY.intValue())
			{
				PropertyRecord prevPropRecord = getPropertyRecord(prevProp);
				if (prevPropRecord == null)
				{
					prevPropRecord = getPropertyStore().getLightRecord(prevProp);
					addPropertyRecord(prevPropRecord);
				}
				Debug.Assert(prevPropRecord.inUse());
				prevPropRecord.setNextProp(nextProp);
			}
			if (nextProp != Record.NO_NEXT_PROPERTY.intValue())
			{
				PropertyRecord nextPropRecord = getPropertyRecord(nextProp);
				if (nextPropRecord == null)
				{
					nextPropRecord = getPropertyStore().getLightRecord(nextProp);
					addPropertyRecord(nextPropRecord);
				}
				Debug.Assert(nextPropRecord.inUse());
				nextPropRecord.setPrevProp(prevProp);
			}
		}

		internal virtual void relChangeProperty(int relId, int propertyId, object @value)
		{
			RelationshipRecord relRecord = getRelationshipRecord(relId);
			if (relRecord == null)
			{
				relRecord = getRelationshipStore().getRecord(relId);
			}
			if (!relRecord.inUse())
			{
				throw new IllegalStateException("Property change on relationship[" + relId + "] illegal since it has been deleted.");
			}
			PropertyRecord propertyRecord = getPropertyRecord(propertyId);
			if (propertyRecord == null)
			{
				propertyRecord = getPropertyStore().getRecord(propertyId);
				addPropertyRecord(propertyRecord);
			}
			if (!propertyRecord.inUse())
			{
				throw new IllegalStateException("Unable to change property[" + propertyId + "] since it is deleted.");
			}
			propertyRecord.setRelId(relId);
			if (propertyRecord.isLight())
			{
				getPropertyStore().makeHeavy(propertyRecord);
			}
			if (propertyRecord.getType() == PropertyType.STRING)
			{
				foreach (DynamicRecord record in propertyRecord.getValueRecords())
				{
					if (record.inUse())
					{
						record.setInUse(false, PropertyType.STRING.intValue());
					}
				}
			}
			else if (propertyRecord.getType() == PropertyType.ARRAY)
			{
				foreach (DynamicRecord record in propertyRecord.getValueRecords())
				{
					if (record.inUse())
					{
						record.setInUse(false, PropertyType.ARRAY.intValue());
					}
				}
			}
			getPropertyStore().encodeValue(propertyRecord, @value);
			addPropertyRecord(propertyRecord);
		}

		internal virtual void nodeChangeProperty(int nodeId, int propertyId, object @value)
		{
			NodeRecord nodeRecord = getNodeRecord(nodeId);
			if (nodeRecord == null)
			{
				nodeRecord = getNodeStore().getRecord(nodeId);
			}
			if (!nodeRecord.inUse())
			{
				throw new IllegalStateException("Property change on node[" + nodeId + "] illegal since it has been deleted.");
			}
			PropertyRecord propertyRecord = getPropertyRecord(propertyId);
			if (propertyRecord == null)
			{
				propertyRecord = getPropertyStore().getRecord(propertyId);
				addPropertyRecord(propertyRecord);
			}
			if (!propertyRecord.inUse())
			{
				throw new IllegalStateException("Unable to change property[" + propertyId + "] since it is deleted.");
			}
			propertyRecord.setNodeId(nodeId);
			if (propertyRecord.isLight())
			{
				getPropertyStore().makeHeavy(propertyRecord);
			}
			if (propertyRecord.getType() == PropertyType.STRING)
			{
				foreach (DynamicRecord record in propertyRecord.getValueRecords())
				{
					if (record.inUse())
					{
						record.setInUse(false, PropertyType.STRING.intValue());
					}
				}
			}
			else if (propertyRecord.getType() == PropertyType.ARRAY)
			{
				foreach (DynamicRecord record in propertyRecord.getValueRecords())
				{
					if (record.inUse())
					{
						record.setInUse(false, PropertyType.ARRAY.intValue());
					}
				}
			}
			getPropertyStore().encodeValue(propertyRecord, @value);
			addPropertyRecord(propertyRecord);
		}

		internal virtual void relAddProperty(int relId, int propertyId, PropertyIndex index, object @value)
		{
			RelationshipRecord relRecord = getRelationshipRecord(relId);
			if (relRecord == null)
			{
				relRecord = getRelationshipStore().getRecord(relId);
				addRelationshipRecord(relRecord);
			}
			if (!relRecord.inUse())
			{
				throw new IllegalStateException("Property add on relationship[" + relId + "] illegal since it has been deleted.");
			}
			PropertyRecord propertyRecord = new PropertyRecord(propertyId);
			propertyRecord.setInUse(true);
			propertyRecord.setCreated();
			if (relRecord.getNextProp() != Record.NO_NEXT_RELATIONSHIP.intValue())
			{
				PropertyRecord prevProp = getPropertyRecord(relRecord.getNextProp());
				if (prevProp == null)
				{
					prevProp = getPropertyStore().getLightRecord(relRecord.getNextProp());
					addPropertyRecord(prevProp);
				}
				Debug.Assert(prevProp.getPrevProp() == Record.NO_PREVIOUS_PROPERTY.intValue());
				prevProp.setPrevProp(propertyId);
				propertyRecord.setNextProp(prevProp.getId());
			}
			int keyIndexId = index.getKeyId();
			propertyRecord.setKeyIndexId(keyIndexId);
			getPropertyStore().encodeValue(propertyRecord, @value);
			relRecord.setNextProp(propertyId);
			addPropertyRecord(propertyRecord);
		}

		internal virtual void nodeAddProperty(int nodeId, int propertyId, PropertyIndex index, object @value)
		{
			NodeRecord nodeRecord = getNodeRecord(nodeId);
			if (nodeRecord == null)
			{
				nodeRecord = getNodeStore().getRecord(nodeId);
				addNodeRecord(nodeRecord);
			}
			if (!nodeRecord.inUse())
			{
				throw new IllegalStateException("Property add on node[" + nodeId + "] illegal since it has been deleted.");
			}

			PropertyRecord propertyRecord = new PropertyRecord(propertyId);
			propertyRecord.setInUse(true);
			propertyRecord.setCreated();
		// encoding has to be set here before anything is change
		// (exception is thrown in encodeValue now and tx not marked
		// rollback only
			getPropertyStore().encodeValue(propertyRecord, @value);
			if (nodeRecord.getNextProp() != Record.NO_NEXT_PROPERTY.intValue())
			{
				PropertyRecord prevProp = getPropertyRecord(nodeRecord.getNextProp());
				if (prevProp == null)
				{
					prevProp = getPropertyStore().getLightRecord(nodeRecord.getNextProp());
					addPropertyRecord(prevProp);
				}
				Debug.Assert(prevProp.getPrevProp() == Record.NO_PREVIOUS_PROPERTY.intValue());
				prevProp.setPrevProp(propertyId);
				propertyRecord.setNextProp(prevProp.getId());
			}
			int keyIndexId = index.getKeyId();
			propertyRecord.setKeyIndexId(keyIndexId);
			nodeRecord.setNextProp(propertyId);
			addPropertyRecord(propertyRecord);
		}

		internal virtual void relationshipCreate(int id, int firstNodeId, int secondNodeId, int type)
		{
			NodeRecord firstNode = getNodeRecord(firstNodeId);
			if (firstNode == null)
			{
				firstNode = getNodeStore().getRecord(firstNodeId);
				addNodeRecord(firstNode);
			}
			if (!firstNode.inUse())
			{
				throw new IllegalStateException("First node[" + firstNodeId + "] is deleted and cannot be used to create a relationship");
			}
			NodeRecord secondNode = getNodeRecord(secondNodeId);
			if (secondNode == null)
			{
				secondNode = getNodeStore().getRecord(secondNodeId);
				addNodeRecord(secondNode);
			}
			if (!secondNode.inUse())
			{
				throw new IllegalStateException("Second node[" + secondNodeId + "] is deleted and cannot be used to create a relationship");
			}
			RelationshipRecord record = new RelationshipRecord(id, firstNodeId, secondNodeId, type);
			record.setInUse(true);
			record.setCreated();
			addRelationshipRecord(record);
			connectRelationship(firstNode, secondNode, record);
		}

		private void connectRelationship(NodeRecord firstNode, NodeRecord secondNode, RelationshipRecord rel)
		{
			Debug.Assert(firstNode.getNextRel() != rel.getId());
			Debug.Assert(secondNode.getNextRel() != rel.getId());
			rel.setFirstNextRel(firstNode.getNextRel());
			rel.setSecondNextRel(secondNode.getNextRel());
			if (firstNode.getNextRel() != Record.NO_NEXT_RELATIONSHIP.intValue())
			{
				Relationship lockableRel = new LockableRelationship(firstNode.getNextRel());
				getWriteLock(lockableRel);
				RelationshipRecord nextRel = getRelationshipRecord(firstNode.getNextRel());
				if (nextRel == null)
				{
					nextRel = getRelationshipStore().getRecord(firstNode.getNextRel());
					addRelationshipRecord(nextRel);
				}
				if (nextRel.getFirstNode() == firstNode.getId())
				{
					nextRel.setFirstPrevRel(rel.getId());
				}
				else if (nextRel.getSecondNode() == firstNode.getId())
				{
					nextRel.setSecondPrevRel(rel.getId());
				}
				else
				{
					throw new InvalidRecordException(firstNode + " dont match " + nextRel);
				}
			}
			if (secondNode.getNextRel() != Record.NO_NEXT_RELATIONSHIP.intValue())
			{
				Relationship lockableRel = new LockableRelationship(secondNode.getNextRel());
				getWriteLock(lockableRel);
				RelationshipRecord nextRel = getRelationshipRecord(secondNode.getNextRel());
				if (nextRel == null)
				{
					nextRel = getRelationshipStore().getRecord(secondNode.getNextRel());
					addRelationshipRecord(nextRel);
				}
				if (nextRel.getFirstNode() == secondNode.getId())
				{
					nextRel.setFirstPrevRel(rel.getId());
				}
				else if (nextRel.getSecondNode() == secondNode.getId())
				{
					nextRel.setSecondPrevRel(rel.getId());
				}
				else
				{
					throw new InvalidRecordException(secondNode + " dont match " + nextRel);
				}
			}
			firstNode.setNextRel(rel.getId());
			secondNode.setNextRel(rel.getId());
		}

		internal virtual void nodeCreate(int nodeId)
		{
			NodeRecord nodeRecord = new NodeRecord(nodeId);
			nodeRecord.setInUse(true);
			nodeRecord.setCreated();
			addNodeRecord(nodeRecord);
		}

		internal virtual string getPropertyIndex(int id)
		{
			PropertyIndexStore indexStore = getPropertyStore().getIndexStore();
			PropertyIndexRecord index = getPropertyIndexRecord(id);
			if (index == null)
			{
				index = indexStore.getRecord(id);
			}
			if (index.isLight())
			{
				indexStore.makeHeavy(index);
			}
			return indexStore.getStringFor(index);
		}

		internal virtual PropertyIndexData[] getPropertyIndexes(int count)
		{
			PropertyIndexStore indexStore = getPropertyStore().getIndexStore();
			return indexStore.getPropertyIndexes(count);
		}

		internal virtual void createPropertyIndex(int id, string key)
		{
			PropertyIndexRecord record = new PropertyIndexRecord(id);
			record.setInUse(true);
			record.setCreated();
			PropertyIndexStore propIndexStore = getPropertyStore().getIndexStore();
			int keyBlockId = propIndexStore.nextKeyBlockId();
			record.setKeyBlockId(keyBlockId);
			int length = key.Length;
			char[] chars = new char[length];
			key.getChars(0, length, chars, 0);
			Collection<DynamicRecord> keyRecords = propIndexStore.allocateKeyRecords(keyBlockId, chars);
			foreach (DynamicRecord keyRecord in keyRecords)
			{
				record.addKeyRecord(keyRecord);
			}
			addPropertyIndexRecord(record);
		}

		internal virtual void relationshipTypeAdd(int id, string name)
		{
			RelationshipTypeRecord record = new RelationshipTypeRecord(id);
			record.setInUse(true);
			record.setCreated();
			int blockId = getRelationshipTypeStore().nextBlockId();
			record.setTypeBlock(blockId);
			int length = name.Length;
			char[] chars = new char[length];
			name.getChars(0, length, chars, 0);
			Collection<DynamicRecord> typeNameRecords = getRelationshipTypeStore().allocateTypeNameRecords(blockId, chars);
			foreach (DynamicRecord typeRecord in typeNameRecords)
			{
				record.addTypeRecord(typeRecord);
			}
			addRelationshipTypeRecord(record);
		}

		[Serializable]
		internal class CommandSorter : Comparator<Command>
		{
			public virtual int compare(Command o1, Command o2)
			{
				int id1 = o1.Key;
				int id2 = o2.Key;
				return id1 - id2;
			}

			public override bool Equals(object o)
			{
				if (o is CommandSorter)
				{
					return true;
				}
				return false;
			}

			public override int GetHashCode()
			{
				return 3217;
			}
		}

		internal virtual void addNodeRecord(NodeRecord record)
		{
			nodeRecords.put(record.getId(), record);
		}

		internal virtual NodeRecord getNodeRecord(int nodeId)
		{
			return nodeRecords.get(nodeId);
		}

		internal virtual void addRelationshipRecord(RelationshipRecord record)
		{
			relRecords.put(record.getId(), record);
		}

		internal virtual RelationshipRecord getRelationshipRecord(int relId)
		{
			return relRecords.get(relId);
		}

		internal virtual void addPropertyRecord(PropertyRecord record)
		{
			propertyRecords.put(record.getId(), record);
		}

		internal virtual PropertyRecord getPropertyRecord(int propertyId)
		{
			return propertyRecords.get(propertyId);
		}

		internal virtual void addRelationshipTypeRecord(RelationshipTypeRecord record)
		{
			relTypeRecords.put(record.getId(), record);
		}

		internal virtual void addPropertyIndexRecord(PropertyIndexRecord record)
		{
			propIndexRecords.put(record.getId(), record);
		}

		internal virtual PropertyIndexRecord getPropertyIndexRecord(int id)
		{
			return propIndexRecords.get(id);
		}

		private class LockableRelationship : Relationship
		{
			private readonly int id;

			internal LockableRelationship(int id)
			{
				this.id = id;
			}

			public virtual void delete()
			{
				throw new UnsupportedOperationException("Lockable rel");
			}

			public virtual Node getEndNode()
			{
				throw new UnsupportedOperationException("Lockable rel");
			}

			public virtual long getId()
			{
				return this.id;
			}

			public virtual Node[] getNodes()
			{
				throw new UnsupportedOperationException("Lockable rel");
			}

			public virtual Node getOtherNode(Node node)
			{
				throw new UnsupportedOperationException("Lockable rel");
			}

			public virtual object getProperty(string key)
			{
				throw new UnsupportedOperationException("Lockable rel");
			}

			public virtual object getProperty(string key, object defaultValue)
			{
				throw new UnsupportedOperationException("Lockable rel");
			}

			public virtual IEnumerable <string> getPropertyKeys()
			{
				throw new UnsupportedOperationException("Lockable rel");
			}

			public virtual IEnumerable <object> getPropertyValues()
			{
				throw new UnsupportedOperationException("Lockable rel");
			}

			public virtual Node getStartNode()
			{
				throw new UnsupportedOperationException("Lockable rel");
			}

			public virtual RelationshipType getType()
			{
				throw new UnsupportedOperationException("Lockable rel");
			}

			public virtual bool isType(RelationshipType type)
			{
				throw new UnsupportedOperationException("Lockable rel");
			}

			public virtual bool hasProperty(string key)
			{
				throw new UnsupportedOperationException("Lockable rel");
			}

			public virtual object removeProperty(string key)
			{
				throw new UnsupportedOperationException("Lockable rel");
			}

			public virtual void setProperty(string key, object @value)
			{
				throw new UnsupportedOperationException("Lockable rel");
			}

			public override bool Equals(object o)
			{
				if (!(o is Relationship))
				{
					return false;
				}
				return this.getId() == ((Relationship) o).getId();
			}

			public override int GetHashCode()
			{
				return id;
			}

			public override string ToString()
			{
				return "Lockable relationship #" + this.getId();
			}
		}
	}
}